package com.rs.game;

import java.util.concurrent.CopyOnWriteArrayList;

import com.rs.cache.Cache;
import com.rs.cache.loaders.ObjectDefinitions;
import com.rs.cores.CoresManager;
import com.rs.game.item.FloorItem;
import com.rs.game.player.Player;
import com.rs.io.InputStream;
import com.rs.utils.MapContainersXteas;
import com.rs.utils.NPCSpawns;
import com.rs.utils.ObjectSpawns;

public class Region {
	
	private int regionId;
	private RegionMap map;
	private CopyOnWriteArrayList<Integer> playersIndexes;
	private CopyOnWriteArrayList<Integer> npcsIndexes;
	private CopyOnWriteArrayList<WorldObject> spawnedObjects;
	private CopyOnWriteArrayList<FloorItem> floorItems;
	private WorldObject[][][][] objects;
	private int loadMapStage;
	private boolean loadedNPCSpawns;
	private boolean loadedObjectSpawns;
	private int musicId;

	  public static final String getMusicName(int regionId) {
	    	switch(regionId)  {
		/*
		 * Lumbridge, falador and region.
		 */
	    	case 12850:
	    	case 12849:
	    		return "Dream";
	    	case 12851:
	    		return "Autumn Voyage";
	    	case 12338:
	    	case 12339:
	    		return "Start";
	    	case 12593:
	    		return "Book of Spells";
	    	case 12083:
	    		return "Competition";
	    	case 12082:
	    		return "Sea Shanty";
	    	case 12081:
		case 11573:
		case 11574:
		case 11575:
		case 11317:
	    		return "Attention";
	    	case 11825:
	    		return "Arrival";
	    	case 11823:
	    		return "Mudskipper Melody";
	    	case 11824:
	    		return "Sea Shanty2";
	    	case 11826:
	    		return "Fanfare";
	    	case 11570:
	    		return "Wandar";
	    	case 11827:
	    		return "Splendour";
	    	case 11828:
	    		return "Long Way Home";
	    	case 11829:
	    		return "Workshop";
	    	case 12341:
	    		return "Barbarianims";
	    	case 12853:
	    	case 12854:
	    		return "Greatness";
	    	case 12852:
	    		return "Expanse";
	    	case 12597:
	    		return "Bounty Hunter Level 1";
	    	case 12594:
	    		return "Doorways";
	    	case 12855:
	    		return "Crystal Sword";
	    	case 12856:
	    		return "Dark";
	    	case 12599:
		case 11320:
	    		return "Bounty Hunter Level 3";
	    	case 12598:
	    		return "Doorways";
	    	case 12342:
	    	case 12343:
	    		return "Spirit";
	    	case 12600:
	    		return "Scape Wild";
		/*
		 * Camelot and region.
		 */
	    	case 11062:
		case 10806:
		case 10805:
	    		return "Camelot";
		case 10550:
			return "Talking Forest";
		case 10549:
			return "Lasting";
		case 10548:
			return "Wonderous";
		case 10547:
			return "Baroque";
		case 10291:
		case 10292:
		case 10293:
		
			return "Knightly";
			
		/*
		 * Mobilies Armies.
		 */
		case 9516:
			return "Command Centre";
	    	default:
	    		return null;
	    	}
	    }




	   
    private static final int getMusicId(int regionId) {
    	return Cache.getCacheFileManagers()[6].getContainerId(getMusicName(regionId));
    }
    
	public Region(int regionId) {
		this.regionId = regionId;
		musicId = getMusicId(regionId);
		//indexes null by default cuz we dont want them on mem for regions that players cant go in
		checkLoadMap();
	}
	
	public void removeMapFromMemory() {
		if(loadMapStage == 2 && (playersIndexes == null || playersIndexes.isEmpty()) && (npcsIndexes == null || npcsIndexes.isEmpty())) {
			objects = null;
			map = null;
			loadMapStage = 0;
		}
	}
	
	public RegionMap forceGetRegionMap() {
		if(map == null)
			map = new RegionMap(regionId);
		return map;
	}
	
	public void addMapObject(WorldObject object, int x, int y) {
		if(map == null)
			map = new RegionMap(regionId);
		int plane = object.getPlane();
		int type = object.getType();
		int rotation = object.getRotation();
		if(x < 0 || y < 0 || x >= map.getMasks()[plane].length || y >= map.getMasks()[plane][x].length)
			return;
		ObjectDefinitions objectDefinition = ObjectDefinitions.getObjectDefinitions(object.getId()); //load here
		if(type == 22 ? objectDefinition.getRouteFinderInt() != 0 : objectDefinition.getRouteFinderInt() == 0)
			return;
		if (type >= 0 && type <= 3)
			map.addWall(plane, x, y, type, rotation, objectDefinition.isSolid(), true);
		else if (type >= 9 && type <= 21) {
			int sizeX;
			int sizeY;
			if (rotation != 1 && rotation != 3) {
				sizeX = objectDefinition.getSizeX();
				sizeY = objectDefinition.getSizeY();
			} else {
				sizeX = objectDefinition.getSizeY();
				sizeY = objectDefinition.getSizeX();
			}
			map.addObject(plane, x, y, sizeX, sizeY, objectDefinition.isSolid(), true);
		} else if (type == 22) {
		//	map.addFloor(plane, x, y);
		}
	}

	//override by static region to empty
	public void checkLoadMap() {
		if(loadMapStage == 0) {
			loadMapStage = 1;
			//lets use slow executor, if we take 1-3sec to load objects who cares? what maters are the players on the loaded regions lul
			CoresManager.slowExecutor.submit(new Runnable() {
				@Override
				public void run() {
					loadRegionMap();
					loadMapStage = 2;
					if(!loadedObjectSpawns) {
						loadObjectSpawns();
						loadedObjectSpawns = true;
					}
					if(!loadedNPCSpawns) {
						loadNPCSpawns();
						loadedNPCSpawns = true;
					}
				}
			});
		}
	}
	
	private void loadNPCSpawns() {
		NPCSpawns.loadNPCSpawns(regionId);
	}
	
	private void loadObjectSpawns() {
		ObjectSpawns.loadObjectSpawns(regionId);
	}
	
	public int getRegionId() {
		return regionId;
	}
	
	private void loadRegionMap() {
		int regionX = (regionId >> 8) * 64;
		int regionY = (regionId & 0xff) * 64;
		int landContainerId = Cache.getCacheFileManagers()[5].getContainerId("l" + ((regionX >> 3) / 8)+ "_" + ((regionY >> 3) / 8));
		if(landContainerId == -1)
			return;
		byte[] landContainerData = Cache.getCacheFileManagers()[5].getFileData(landContainerId, 0, MapContainersXteas.getMapContainerXteas(regionId));
		if(landContainerData == null)
			return;
		int mapContainerId = Cache.getCacheFileManagers()[5].getContainerId("m" + ((regionX >> 3) / 8)+ "_" + ((regionY >> 3) / 8));
		byte[] mapContainerData = mapContainerId == -1 ? null : Cache.getCacheFileManagers()[5].getFileData(mapContainerId, 0);
		byte[][][] mapSettings = mapContainerData == null ? null : new byte[4][64][64];
		if(mapContainerData != null) {
			InputStream mapStream = new InputStream(mapContainerData);
			for (int plane = 0; plane < 4; plane++) {
				for (int x = 0; x < 64; x++) {
					for (int y = 0; y < 64; y++) {
						while (true) {
							int value = mapStream.readUnsignedByte();
							if (value == 0) {
								break;
							} else if (value == 1) {
								mapStream.readByte();
								break;
							} else if (value <= 49) {
								mapStream.readByte();
									
							} else if (value <= 81) {
								mapSettings[plane][x][y] = (byte) (value - 49);
							}
						}
					}
				}
			}
		}
		for (int plane = 0; plane < 4; plane++) {
			for (int x = 0; x < 64; x++) {
				for (int y = 0; y < 64; y++) {
					if((mapSettings[plane][x][y] & 0x1) == 1 && (mapSettings[1][x][y] & 2) != 2)
						forceGetRegionMap().clipTile(plane, x, y);
				}
			}
		}
		InputStream landStream = new InputStream(landContainerData);
		int objectId = -1;
		int incr;
		while ((incr = landStream.readSmart2()) != 0) {
			objectId += incr;
			int location = 0;
			int incr2;
			while ((incr2 = landStream.readUnsignedSmart()) != 0) {
				location += incr2 - 1;
				int localX = (location >> 6 & 0x3f);
				int localY = (location & 0x3f);
				int plane = location >> 12;
				int objectData = landStream.readUnsignedByte();
				int type = objectData >> 2;
				int rotation = objectData & 0x3;
				if (localX < 0 || localX >= 64 || localY < 0 || localY >= 64)
					continue;
				int objectPlane = plane;
				if (mapSettings != null && (mapSettings[1][localX][localY] & 2) == 2)
					objectPlane--;
				if (objectPlane < 0 || objectPlane >= 4 || plane < 0 || plane >= 4)
					continue;
				addObject(new WorldObject(objectId, type, rotation, localX+regionX, localY+regionY, objectPlane), plane, localX, localY);
			}
		}
	}
	
	public void addObject(WorldObject object, int plane, int localX, int localY) {
		addMapObject(object, localX, localY);
		if(objects == null)
			objects = new WorldObject[4][64][64][];
		WorldObject[] tileObjects = objects[plane][localX][localY];
		if(tileObjects == null)
			objects[plane][localX][localY] = new WorldObject[] {object};
		else {
			WorldObject[] newTileObjects = new WorldObject[tileObjects.length+1];
			newTileObjects[tileObjects.length] = object;
			System.arraycopy(tileObjects, 0, newTileObjects, 0, tileObjects.length);
			objects[plane][localX][localY] = newTileObjects;
		}
	}
	
	public CopyOnWriteArrayList<Integer> getPlayerIndexes() {
		return playersIndexes;
	}
	
	public CopyOnWriteArrayList<Integer> getNPCsIndexes() {
		return npcsIndexes;
	}
	
	public void addPlayerIndex(int index) {
		//creates list if doesnt exist
		if(playersIndexes == null)
			playersIndexes = new CopyOnWriteArrayList<Integer>();
			playersIndexes.add(index);
	}
	
	public void addNPCIndex(int index) {
		//creates list if doesnt exist
		if(npcsIndexes == null)
			npcsIndexes = new CopyOnWriteArrayList<Integer>();
			npcsIndexes.add(index);
	}
	
	public void removePlayerIndex(Integer index) {
			if(playersIndexes == null) //removed region example cons or dung
				return;
			playersIndexes.remove(index);
	}
	
	public void removeNPCIndex(Integer index) {
			if(npcsIndexes == null) //removed region example cons or dung
				return;
			npcsIndexes.remove(index);
	}
	
	public WorldObject getObject(int plane, int x, int y) {
		WorldObject[] objects = getObjects(plane, x,y);
		if(objects == null)
			return null;
		return objects[0];
	}
	
	//override by static region to get objects from needed
	public WorldObject[] getObjects(int plane, int x, int y) {
		checkLoadMap();
		//if objects just loaded  now will return null, anyway after they load will return correct so np
		if(objects == null)
			return null;
		return objects[plane][x][y];
	}
	
	public WorldObject getObject(int id, WorldTile tile) {
		int absX = (regionId >> 8) * 64;
		int absY = (regionId & 0xff) * 64;
		int localX = tile.getX()-absX;
		int localY = tile.getY()-absY;
		if(localX < 0 || localY < 0 || localX >= 64 || localY >= 64)
			return null;
		WorldObject spawnedObject = getSpawnedObject(tile);
		if(spawnedObject != null)
			return spawnedObject;
		WorldObject[] mapObjects = getObjects(tile.getPlane(), localX, localY);
		if(mapObjects == null)
			return null;
		for(WorldObject object : mapObjects)
			if(object.getId() == id)
				return object;
		return null;
	}
	
	public WorldObject getSpawnedObject(WorldTile tile) {
		if(spawnedObjects == null)
			return null;
		for(WorldObject object : spawnedObjects)
			if(object.getX() == tile.getX() && object.getY() == tile.getY() && object.getPlane() == tile.getPlane())
				return object;
		return null;
	}
	
	public void addObject(WorldObject object) {
		if(spawnedObjects == null)
			spawnedObjects = new CopyOnWriteArrayList<WorldObject>();
		spawnedObjects.add(object);
	}
	
	public void removeObject(WorldObject object) {
		if(spawnedObjects == null)
			return;
		spawnedObjects.remove(object);
	}
	
	public CopyOnWriteArrayList<WorldObject> getSpawnedObjects() {
		return spawnedObjects;
	}
	
	public WorldObject getRealObject(WorldObject spawnObject) {
		int absX = (regionId >> 8) * 64;
		int absY = (regionId & 0xff) * 64;
		int localX = spawnObject.getX()-absX;
		int localY = spawnObject.getY()-absY;
		WorldObject[] mapObjects = getObjects(spawnObject.getPlane(), localX, localY);
		if(mapObjects == null)
			return null;
		for(WorldObject object : mapObjects)
			if(object.getType() == spawnObject.getType())
				return object;
		return null;
	}
	
	public boolean containsObject(int id, WorldTile tile) {
		int absX = (regionId >> 8) * 64;
		int absY = (regionId & 0xff) * 64;
		int localX = tile.getX()-absX;
		int localY = tile.getY()-absY;
		if(localX < 0 || localY < 0 || localX >= 64 || localY >= 64)
			return false;
		WorldObject spawnedObject = getSpawnedObject(tile);
		if(spawnedObject != null)
			return spawnedObject.getId() == id;
		WorldObject[] mapObjects = getObjects(tile.getPlane(), localX, localY);
		if(mapObjects == null)
			return false;
		for(WorldObject object : mapObjects)
			if(object.getId() == id)
				return true;
		return false;
	}
	
	
	//overrided by static region to get mask from needed region
	public int getMask(int plane, int localX, int localY) {
		if(map == null)
			return 2097152; //cliped tile
		return map.getMasks()[plane][localX][localY];
	}
	
	public CopyOnWriteArrayList<FloorItem> forceGetFloorItems() {
		if(floorItems == null)
			floorItems = new CopyOnWriteArrayList<FloorItem>();
		return floorItems;
	}
	public CopyOnWriteArrayList<FloorItem> getFloorItems() {
		return floorItems;
	}
	
	public FloorItem getGroundItem(int id, WorldTile tile, Player player) {
		if(floorItems == null)
			return null;
		for(FloorItem item : floorItems) {
			if((item.isInvisible() || item.isGrave()) && player != item.getOwner())
				continue;
			if(item.getId() == id && tile.getX() == item.getTile().getX() && tile.getY() == item.getTile().getY() && tile.getPlane() == item.getTile().getPlane())
				return item;
		}
		return null;
	}

	public int getMusicId() {
		return musicId;
	}
	
}
